<?php
/**
 * Created by PhpStorm.
 * User: yf
 * Date: 2018/11/26
 * Time: 12:31 PM
 */

namespace App\Model;


use App\Common\Exception\ThrowException;
use App\Utility\Pool\MysqlObject;
use App\Utility\Pool\MysqlPool;
use App\Utility\Pool\RedisObject;
use App\Utility\Pool\RedisPool;

/**
 * model写法1
 * 通过传入mysql连接去进行处理
 * Class BaseModel
 * @package App\Model
 */
class BaseModel
{
    private $db;
    private $redis;
    protected $table;
    protected $disuse = [];

    /**
     * 数据表字段信息 留空则自动获取
     * @var array
     */
    protected $field = [];                //字段名称

    protected $autoWriteTimestamp = false;    //是否开启时间自动

    protected $createTime = 'create_time';  //创建时间字段

    protected $updateTime = 'updateTime';  //更新时间字段

    function __construct()
    {
        $this->db = MysqlPool::defer(); //获取联接池对象
        $this->redis = RedisPool::defer(); //获取联接池对象
        $this->table = $this->table ?: $this->getTable();
    }

    /**
     * db 数据联接
     * @return MysqlObject
     */
    protected function getDb(): MysqlObject
    {
        return $this->db;
    }

    /**
     * redis联接
     * @return RedisObject
     */
    public function getRedis(): RedisObject
    {
        return $this->redis;
    }

    /**
     * db 联接
     * @return MysqlObject
     */
    function getDbConnection(): MysqlObject
    {
        return $this->db;
    }

    /**
     * 获取模型表名称
     * @param string $name
     * @return string
     */
    public function getTable($name = '')
    {
        $name = str_replace('\\', '/', static::class);
        $name = basename($name);
        $conf = \EasySwoole\EasySwoole\Config::getInstance()->getConf('MYSQL');
        return $conf['prefix'] . self::parseName($name);
    }

    /**
     * 字符串命名风格转换
     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
     * @access public
     * @param string $name 字符串
     * @param integer $type 转换类型
     * @param bool $ucfirst 首字母是否大写（驼峰规则）
     * @return string
     */
    public static function parseName($name, $type = 0, $ucfirst = true)
    {
        if ($type) {
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
                return strtoupper($match[1]);
            }, $name);
            return $ucfirst ? ucfirst($name) : lcfirst($name);
        }

        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
    }

    /**
     * 获取分类数据
     * @param array $condition
     * @param int $page
     * @param int $pageSize
     * @return array
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\Option
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function getPageAll($condition = [], int $page = 1, $pageSize = 10, $columns = '*'): array
    {
        $this->getCondition($condition);
        $list = $this->getDb()
            ->withTotalCount()
            ->get($this->table, [$pageSize * ($page - 1), $pageSize], $columns);
        $total = $this->getDb()->getTotalCount();
        return ['total' => $total, 'list' => $list];
    }

    /**
     * 获取所有数据
     * @param array $condition
     * @param int $page
     * @param int $pageSize
     * @return array
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\Option
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function getAll($condition = [], $columns = '*'): array
    {
        $this->getCondition($condition);
        return $this->select($columns);
    }

    /**
     * 选择数据列表
     * @param $columns
     * @param null $numRow
     * @return MysqlObject|mixed
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function select($columns, $numRow = null)
    {
        return $this->getDb()->get($this->table, $numRow, $columns);
    }

    /**
     * 获取单列为键值，对应字段为键对应的值
     * @param $condition
     * @param string $fieldkey
     * @param string $fieldValue
     * @param null $limit
     * @return array
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function getFiledColumnValue($condition, $fieldkey = 'name', $fieldValue = 'value', $limit = null)
    {
        $this->getCondition($condition);
        $res = $this->getDb()->get($this->table, $limit, "{$fieldValue} AS retval,{$fieldkey} AS retkey");
        if (!$res) {
            return [];
        }
        return array_column($res, 'retval', 'retkey');
    }

    /**
     * 获取单条数据
     * @param $condition
     * @param string $columns
     * @return \EasySwoole\Mysqli\Mysqli|mixed|null
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function findOne($condition, $columns = '*')
    {
        $this->getCondition($this->analysisWhere($condition));
        return $this->getDb()->getOne($this->table, $columns);
    }

    /**
     * 数据的新增
     * @param $data
     * @return bool|int
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function addData($data)
    {
        try {
            if (!$res = $this->getDb()->insert($this->table, $data)) {
                echo $this->getDb()->getLastQuery();
                throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
            }
            echo $this->getDb()->getLastQuery();
            return $res;
        } catch (\Exception $e) {
            echo $this->getDb()->getLastQuery();
            throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
        }

    }

    /**
     * 多条数据新增
     * @param $data
     * @param null $fieldValue
     * @return array|bool
     * @throws ThrowException
     * @throws \Throwable
     */
    public function addDataMuti($data, $fieldValue = null)
    {
        try {
            if (!$res = $this->getDb()->insertMulti($this->table, $data, $fieldValue)) {
                echo $this->getDb()->getLastQuery();
                throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
            }
            return $res;
        } catch (\Exception $e) {
            echo $this->getDb()->getLastQuery();
            throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
        }
    }

    /**
     *  数据更新
     * @param $condition
     * @param $data
     * @return mixed
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function editData($condition, $data)
    {
        $this->getCondition($condition);
        try {
            if (!$res = $this->getDb()->update($this->table, $data)) {
                throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
            }
            return $res;
        } catch (\Exception $e) {
            echo $this->getDb()->getLastQuery();
            throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
        }
    }

    /**
     * 数据删除
     * @param $where
     * @param null $numRows
     * @return bool|null
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function delData($where, $numRows = null)
    {
        try {
            $condition = $this->analysisWhere($where);
            $this->getCondition($condition);
            if (!$res = $this->getDb()->delete($this->table, $numRows)) {
                throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
            }
            return $res;
        } catch (\Exception $e) {
            throw new ThrowException(ThrowException::ERROR_DATABASE); //数据操作异常
        }

    }


    /**
     * 根据条件查询某个字段的值
     * @param $where
     * @param $field
     * @return array|null
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function getFiledValue($where, $field)
    {
        $this->getCondition($this->analysisWhere($where));
        return $this->value($field);

    }

    /**
     * 获取字段值
     * @param $filed
     * @return array|null
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function value($filed)
    {
        return $this->getDb()->getValue($this->table, $filed);
    }

    /***
     * 表左联接
     * @param $condition
     * @param $table
     * @param $on
     * @param string $columns
     * @return MysqlObject|mixed
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\JoinFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function getJoinLeft($condition, $table, $on, $columns = '*')
    {
        $this->getCondition($condition);
        return $this->getDb()->join($table, $on, 'left')->get($this->table, null, $columns);
    }

    /**
     * 左联接
     * @param $table
     * @param $on
     * @param string $columns
     * @return $this
     * @throws \EasySwoole\Mysqli\Exceptions\JoinFail
     */
    public function leftJoin($table, $on, $columns = '*')
    {
        $this->getDb()->join($table, $on, 'left');
        return $this;
    }

    /**
     * 数据更新与编辑
     * @param $data 数据
     * @param array $where 条件
     * @return bool|int|mixed
     * @throws \EasySwoole\Mysqli\Exceptions\ConnectFail
     * @throws \EasySwoole\Mysqli\Exceptions\PrepareQueryFail
     * @throws \Throwable
     */
    public function updateOrCreateData($data, $where = [], $append = [], $primaryKey = 'id')
    {

        if (empty($where['id'])) {
            if (isset($data[$primaryKey])) {
                unset($data[$primaryKey]);
            }
            return $this->addData($this->checkAllowFields($data, $append));
        } else {
            $condition = $this->analysisWhere($where);
            return $this->editData($condition, $this->checkAllowFields($data, $append));
        }
    }

    /**
     * 设置允许写入的字段
     * @access public
     * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段
     * @return $this
     */
    public function allowField($field)
    {
        if (is_string($field)) {
            $field = explode(',', $field);
        }
        $this->field = $field;
        return $this;
    }

    /**
     * 条件操作
     * @param $condition
     */
    public function getCondition($condition)
    {
        $allow = ['where', 'whereOr', 'join', 'orderBy', 'groupBy', 'whereNotIn', 'whereIn', 'whereBetween', 'whereNotBetween', 'whereLike', 'whereNotLike', 'whereNull', 'whereEmpty', 'whereNotEmpty'];
        foreach ($condition as $k => $v) {
            if (in_array($k, $allow)) {
                foreach ($v as $item) {
                    $this->getDb()->$k(...$item);
                }
            }
        }
    }

    /**
     *数组进行解析
     * @param $where
     * @return array
     */
    public function analysisWhere($where)
    {
        $condition = [];
        if (is_array($where)) {
            foreach ($where as $k => $v) {
                $keySplit = explode('|', $k);
                if (count($keySplit) == 1) {
                    if (is_array($v)) {
                        $condition['where'][] = [$k, $v[0], $v[1], $v[2]];
                    } else {
                        $condition['where'][] = [$k, $v, '='];
                    }
                } else {
                    switch (strtolower($keySplit[1])) {  //将字符串变小写
                        case 'in':  //之中查询
                            if (empty($keySplit[2])) {
                                $condition['whereIn'][] = [$keySplit[0], $v, 'AND'];
                            } else {
                                $condition['whereIn'][] = [$keySplit[0], $v, $keySplit[2]];
                            }
                            break;
                        case 'like':  //模糊查询
                            if (empty($keySplit[2])) {
                                $condition['whereLike'][] = [$keySplit[0], $v, 'AND'];
                            } else {
                                $condition['whereLike'][] = [$keySplit[0], $v, $keySplit[2]];
                            }
                            break;
                        case '<>':  //不等于
                            if (empty($keySplit[2])) {
                                $condition['where'][] = [$keySplit[0], $v, '<>', 'AND'];
                            } else {
                                $condition['where'][] = [$keySplit[0], $v, '<>', $keySplit[2]];
                            }
                            break;
                        case 'order':
                            $condition['orderBy'][] = [$keySplit[0], $v];
                            break;
                    }

                }
            }

        }
        return $condition;
    }


    /**
     * 检查数据是否允许写入
     * @access protected
     * @param array $append 自动完成的字段列表
     * @return array
     */
    protected function checkAllowFields($data, array $append = [])
    {
        // 检测字段
        if (empty($this->field) || true === $this->field) {
            $this->field = $this->getFields($this->table);
            $field = $this->field;
        } else {
            $field = array_merge($this->field, $append);
            if ($this->autoWriteTimestamp) {
                array_push($field, $this->createTime, $this->updateTime);
            }
        }
        if ($this->disuse) {
            // 废弃字段
            $field = array_diff($field, (array)$this->disuse);
        }

        foreach ($data as $k => $v) {
            $fileds = array_keys($field);
            if (!in_array($k, $fileds)) {
                unset($data[$k]);
            }
        }
        return $data;
    }


    /**
     * 取得数据表的字段信息
     * @access public
     * @param string $tableName
     * @return array
     */
    public function getFields($tableName)
    {
        list($tableName) = explode(' ', $tableName);

        if (false === strpos($tableName, '`')) {
            if (strpos($tableName, '.')) {
                $tableName = str_replace('.', '`.`', $tableName);
            }
            $tableName = '`' . $tableName . '`';
        }

        $sql = 'SHOW COLUMNS FROM ' . $tableName;
        $result = $this->getDb()->rawQuery($sql);
        $info = [];
        if ($result) {
            foreach ($result as $key => $val) {
                $val = array_change_key_case($val);
                $info[$val['field']] = [
                    'name' => $val['field'],
                    'type' => $val['type'],
                    'notnull' => (bool)('' === $val['null']), // not null is empty, null is yes
                    'default' => $val['default'],
                    'primary' => (strtolower($val['key']) == 'pri'),
                    'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
                ];
            }
        }

        return $this->fieldCase($info);
    }

    /**
     * 对返数据表字段信息进行大小写转换出来
     * @access public
     * @param array $info 字段信息
     * @return array
     */
    public function fieldCase($info)
    {
        $info = array_change_key_case($info);
        return $info;
    }

    /**
     * 获取最后输出
     * @return mixed
     */
    public function getLastQuery()
    {
        return $this->getDb()->getLastQuery();
    }

}